"""
Classes necessary for handling the player objects.
Currently: Player & PlayerStatus
$Id: player.py,v 1.13 2004/12/17 09:57:50 krawczyk Exp $
"""

import pygame, copy, math
import config, basic_drawables, game, snowball

class Player(basic_drawables.Drawable_erase, pygame.sprite.Sprite):
    """A Player class.

    action -- which action performs the player? Information is encoded into bits: 0&1: movement (0=standing, 1=moving left, 2=moving right), 0x200=reloading, 0x400=throwing, 0x1000=dying. Actions with Mask 0xF000 should not be interrupted!
    extras -- list of current extras
    extra_current -- which extra is currently selected? -1 none, else number < extras_max
    extra_frame -- Changing extra takes time, this is the framenumber at which the next change is possible
    extra_in_use -- Is the current extra in use?
    gamedata -- Reference to the gamedata
    img -- list of Images for the player
    mov_speed -- moving speed
    life -- How much life energy? 0-255 (Check Get_life_percent)
    keys -- list of keys
    look -- Where looks the player? left/right -1/+1
    pos -- starting position
    snow -- How much snow has a player? 0-200 (Check Get_snow_percent)
    strength -- throwing strength
    """

    #The Actions:
    action_none = 0
    action_moving_left = 1
    action_moving_right = 2
    action_reloading = 0x200
    action_throwing = 0x400
    action_dying = 0x8000
    
    extras_max = 4 #Maximum of 4 extras.
    mov_speed = config.game["player.move_speed"]
    mov_anim_speed = 5
    snow = 0
    snow_max = float(config.game["snowball.maxload"]) #Must be float for display!
    snow_min_throw = config.game["snowball.min_throw"]
    strength = 0
    strength_max = config.game["player.maxstrength"]
    strength_inc = config.game["player.increment"]
    life_max = config.game["player.maxlife"]
    
    show_targetbar = False
    
    def __init__(self, configdict, gamedata):
        """
        configdict -- dictionary with information for the player
        gamedata -- global gamedata structure
        """
        global player_snd_throw, player_snd_hit, player_snd_die, player_snd_extra    

        basic_drawables.Drawable_erase.__init__(self)
        pygame.sprite.Sprite.__init__(self)
        self.gamedata = gamedata
        
        self.life = self.life_max
        self.snd_throw = player_snd_throw
        self.snd_hit   = player_snd_hit
        self.snd_die   = player_snd_die
        self.snd_extra = player_snd_extra
        self._load_images(configdict)
        imgrect = self.img_left[0].get_rect()
        self.imgsize = [imgrect[2], imgrect[3]]
        posx, posy = configdict["startpos"]
        self.pos = [posx, posy]
        self.action = 0
        self.extras = []
        self.extra_current = -1
        self.extra_frame = -1
        self.extra_in_use = False
        if configdict["look"] == -1:
            self.look = -1
        else:
            self.look = +1
        self.keys = configdict["keys"]
        self.counter = 0
        self.target_bar_redx = -1

    def _load_images(self, playerdict):
        """Loads the images. first image is standig, rest is displayed while moveing.
        playerdict -- configuration dictionary of the player.
        """
        self.img_left = []
        self.img_right = []
        #We are using subsurfaces, because when using the reference to the image directly the transparence is changed and on a restart one player can be invisible. The subsurface only shares the pixeldata, therefore this problem does not arise.
        for img in playerdict["images"]:
            s = img.get_rect()
            self.img_left.append(img.subsurface(s))
        for img in playerdict["images.flipped"]:
            s = img.get_rect()
            self.img_right.append(img.subsurface(s))
        self.img_current = 0 #We should be standing

    def _count(self, max):
        "Increment counter and returen True if smaler than max."
        self.counter += 1
        if self.counter >= max:
            self.counter = 0
            return False
        return True

    def Pos(self):
        return self.pos
    
    def Draw(self, on_Surface):
        if self.look < 0:
            imgs = self.img_left
        else:
            imgs = self.img_right
        if self.action == 1 or self.action == 2: #moving left or right
            if self.img_current >= len(imgs)-1:
                if not self._count(self.mov_anim_speed):
                    self.img_current = 1
            else:
                if not self._count(self.mov_anim_speed):
                    self.img_current += 1
            img = imgs[self.img_current]
        else: #Anything else is standing for now...
            img = imgs[0]
        if self.action & self.action_dying: #Player is dying
            self.image_blend_to.set_alpha(self.alphacount)
            img.set_alpha(255-self.alphacount)
            self.dirtyrect.append( on_Surface.blit(self.image_blend_to, self.Pos()) )
        self.dirtyrect.append( on_Surface.blit(img, self.Pos()) )
        if self.show_targetbar:
            self.dirtyrect.append(pygame.draw.line(on_Surface,(0xDA,0,0),(self.target_bar_redx,0),(self.target_bar_redx,600),3))
        return self.dirtyrect

    def Handle_Keys(self, key, keydown):
        """Handle Keys for Player. Returns True if key could be handled.
        gamedata -- gaming data structure
        key -- keycode
        keydown -- True, if key has been pressed
        """
        gamedata = self.gamedata
        if keydown:
            #KEYS DOWN
            if key == self.keys[0]:
                self.Set_Move(-1)
                return True
            elif key == self.keys[1]:
                self.Set_Move(1)
                return True
            elif key == self.keys[2]:
                self.Grab_snow()
                return True
            elif key == self.keys[3]:
                self.Throw(True)
                return True
        else: #KEYS UP
            if key == self.keys[0] and self.Is_Player_moving_left():
                self.Set_Move(0)
                return True
            if key == self.keys[1] and self.Is_Player_moving_right():
                self.Set_Move(0)
                return True
            elif key == self.keys[2]:
                self.Grab_snow(False)
                return True
            elif key == self.keys[3]:
                self.Throw(False)
                return True
            elif key == self.keys[4]:
                self.Extra_Next()
            elif key == self.keys[5]:
                self.Extra_Use()
        return False #No known key found

    def Update(self):
        DATA = self.gamedata
        if self.action == 0:
            pass #no action
        elif self.action == 1:
            if self.pos[0] >= 3:
                self.pos[0] -= self.mov_speed
        elif self.action == 2:
            if self.pos[0] <= config.scrsize[0] - 3 - self.imgsize[0]:
                self.pos[0] += self.mov_speed
        elif self.action & 0x200:
            if self.snow < self.snow_max:
                self.snow += 1
        elif self.action & self.action_throwing: #Throwing
            if self.strength < self.strength_max:
                self.strength += self.strength_inc
                if self.show_targetbar:
                    global snowball_startvel
                    stren = self.Get_strength_percent() * self.strength_max + self.gamedata.player_minstrength
                    self.target_bar_redx = self.pos[0]  -  self.look * stren**2 * snowball_startvel[0] * snowball_startvel[1] * 2/snowball.Snowball.gravity
                    self.target_bar_redx *= 1.025 #Why do I have to correct?
            else:
                self.Throw(False)
        elif self.action & self.action_dying: #Dying
            self.alphacount += 3
            if self.alphacount > 255:
                self.alphacount = 255
            DATA.player_dead = self
        else:
            print "Unknown player action %d!" % self.action
            raise RuntimeError
        self.rect = pygame.Rect(self.pos + self.imgsize) #Rectangle for Spritecollisions


    def Extra_Caught(self, extra):
        "An extra has been caught and it will be put in self.extras, if there is enough space."
        if len(self.extras) < self.extras_max:
            extra.Still_Image(self.gamedata)
            self.extras.append(extra)
            if self.extra_current == -1: self.extra_current = 0
            self.snd_extra.play()
        else:
            pass #Later play a sound, etc.

    def Extra_Next(self):
        "Go to the next extra."
        if self.extra_current != -1 and not self.extra_in_use: #There must be at least one(?) extras and no extra in use
            if self.extra_frame < self.gamedata.framecounter: #Next change allowed
                self.extra_frame += 10
                self.extra_current += 1
                if self.extra_current >= len(self.extras):
                    self.extra_current = 0
        else:
            pass #Or some action...

    def Extra_Use(self):
        "Use the current extra."
        try:
            extra = self._Extra_current()
            type = extra.Type()
            if type == "3" or type=="2" or type=="z" or type=="T":
                self.extra_in_use = True #This is an extra which comes later into effect
            elif type == "L":
                maxlife = self.life_max
                self.life += maxlife / 4 #Reload life energy
                if self.life > maxlife: self.life = maxlife
                self._Extra_remove()
            elif type == "S":
                maxsnow = self.snow_max
                self.snow += maxsnow * extra.Snow_percentage() #Get more snow
                if self.snow > maxsnow: self.snow = maxsnow
                self._Extra_remove()
            else:
                print "Err, unknown Extra Type:",type,"Will ignore..."
        except IndexError:
            pass

    def _Extra_current(self):
        "Return current extra."
        return self.extras[self.extra_current]

    def _Extra_remove(self):
        "Remove the (current) extra."
        extra = self._Extra_current()
        self.extras.remove(extra)
        self.extra_in_use = False
        if self.extra_current >= len(self.extras):
            self.extra_current = len(self.extras)-1

    def Get_life_percent(self):
        return self.life / self.life_max

    def Get_snow_percent(self):
        return self.snow / self.snow_max
    
    def Get_strength_percent(self):
        return float(self.strength) / self.strength_max

    def Grab_snow(self, start=True):
        if start:
            self.action = 0x200
        else:
            self.action &= ~0x200

    def Hit(self, snowball):
        "Player got hit by a snowball, handle this"
        self.life -= snowball.maxhits * snowball.size
        if self.life <= 0:
            self.life = 0
            self.action = self.action_dying
            self.alphacount = 0 #For blending the images
            self.image_blend_to = deadimage
            self.snd_die.play()
	else:
	    self.snd_hit.play()

    def Set_Move(self, dir):
        "Set movement, 0=stop, -1=left, 1=right; If Player is doing something else, returns immediately."
        if self.action not in range(0,2+1):
            return
        if dir == -1:
            self.look = -1
            self.action = 1
        elif dir == 1:
            self.look = 1
            self.action = 2
        elif dir == 0:
            self.action = 0
        else:
            print "Hmm, unknown movement direction ", dir

    def Throw(self, start=True):
        if start:
            self.action = self.action_throwing
        else:
            self.action &= ~self.action_throwing
            p = self.Get_snow_percent() 
            if p > self.snow_min_throw:
                self.snd_throw.play()
                self._Do_throw_snowball()
                self.snow = 0
            self.strength = 0
        print 'Targetbar:',self.target_bar_redx

    def Is_Player_moving_left(self):
        return self.action & 0x3 == 1
    def Is_Player_moving_right(self):
        return self.action & 0x3 == 2
    def Is_Player_not_moving(self):
    	return self.action & 0x3 == 0
    def Is_Throwing(self):
        return self.action & self.action_throwing
    
    def _Do_throw_snowball(self):
        """
        Performs a throw of player "player".
        The percentage of 'snow power' is also given.
        """
        snowp  = self.Get_snow_percent()
        stren  = self.Get_strength_percent() * self.strength_max + self.gamedata.player_minstrength
        vx, vy = snowball_startvel
        px, py = self.Pos()
        vx *= self.look
        px += self.imgsize[0] / 2
        py -= 20
        speed  = stren * vx, stren * vy
        if self.extra_in_use: #We are using an extra
            extra = self._Extra_current()
            if extra.Type() == "3": #3 Snowballs
                self._Extra_remove()
                vx, vy = speed
                snowball.Snowball(self.gamedata.objs_act, snowp, (px, py), (vx*.9, vy*.9))
                snowball.Snowball(self.gamedata.objs_act, snowp, (px, py), (vx*1.1, vy*1.1))
            elif extra.Type() == "2":
                self._Extra_remove()
                vx, vy = speed
                vy *= 1.1
                snowball.Snowball(self.gamedata.objs_act, snowp, (px, py), (vx, vy))
            elif extra.Type() == "z":
                self._Extra_remove()
                snowball.Snowball_slow(self.gamedata.objs_act, snowp, (px, py), speed)
                return #Only generate the slow one
            elif extra.Type() == "T":
                self._Extra_remove()
                snowball.Snowball_tumbling(self.gamedata.objs_act, snowp, (px, py), speed)
                return
        snowball.Snowball(self.gamedata.objs_act, snowp, (px, py) , speed)


class PlayerStatus(basic_drawables.Drawable_erase):
    """Statusclass for the player"""

    blinkfreq = 20
    img_life = None #Image for life status
    img_snow = None #Image for snow status
    img_lifeicon  = None #Icon for life energy
    img_snowicon  = None #Icon for snow
    img_powericon = None #Icon for throw power
    
    def __init__(self, player, pos):
        basic_drawables.Drawable_erase.__init__(self)
        self.player = player
        self.pos = pos
        self.img_life = pygame.image.load(config.game["playerstatus.lifeimg"])
        self.img_snow = pygame.image.load(config.game["playerstatus.snowimg"])
        self.img_lifeicon  = pygame.image.load(config.game["playerstatus.lifeicon"])
        self.img_snowicon  = pygame.image.load(config.game["playerstatus.snowicon"])
        self.img_powericon = pygame.image.load(config.game["playerstatus.powericon"])
        self.last_dirty = []
        self.counter = 0
        self.blink_snow = False
        self.extrablink_direction = 1
        
    def Draw(self, on_Surface):
        size = self.img_life.get_rect()
        sx, sy, sw, sh = size
        px, py = self.Pos()
        #Life Energy
        self.__Blit_rel(on_Surface, self.img_lifeicon, (0, 0))
        self.__Blit_rel(on_Surface, self.img_life, (20, 0), (0, 0, self.player.Get_life_percent()*size[2], size[3]))
        #Snow in Hand
        self.__Blit_rel(on_Surface, self.img_snowicon, (0, 20))
        snowper = self.player.Get_snow_percent()
        if snowper < config.game["snowball.min_throw"]:
            self.counter += 1
            if self.counter >= 10:
                self.blink_snow = not self.blink_snow
                self.counter = 0
        else:
            self.blink_snow = False
        if not self.blink_snow:
            self.__Blit_rel(on_Surface, self.img_snow, (20, 20), (0, 0, snowper*size[2], size[3]))
        #Throw power
        self.__Blit_rel(on_Surface, self.img_powericon, (0, 40))
        self.dirtyrect.append(pygame.draw.rect(on_Surface, (0,0,0), (px+20, py+40, sw, sh), 1))
        self.__Blit_rel(on_Surface, self.img_life, (20, 40), (0, 0, self.player.Get_strength_percent()*size[2], size[3]))
        #Extras
        if self.player.extra_current >= 0: #Player has extras
            pxx = 20
            for extra in self.player.extras:
                self.__Blit_rel(on_Surface, extra.image, (pxx, 60), extra.image.get_rect())
                pxx += 20
            pxx = self.player.extra_current * 20 + px + 20
            if not self.player.extra_in_use:
                blinker = float(self.player.gamedata.framecounter % self.blinkfreq)
                if blinker == 0: self.extrablink_direction *= -1
                if self.extrablink_direction > 0:
                    ctr_perc = (blinker) / (self.blinkfreq-1) * 255
                else:
                    ctr_perc = 255 - (blinker) / (self.blinkfreq-1) * 255
                col = (ctr_perc, ctr_perc, ctr_perc)
            else:
                col = (0xE0, 0, 0)
            sizex, sizey = self.player.extras[self.player.extra_current].image.get_rect()[2:4]
            self.dirtyrect.append(pygame.draw.rect(on_Surface, col, (pxx-2, py+60+2, sizex+4, sizey-4), 1))
        return self.dirtyrect

    def Pos(self):
        return self.pos

    def __Blit_rel(self, surf, img, pos, rect=None):
        dispx, dispy = self.Pos()
        px, py = pos
        if rect != None:
            self.dirtyrect.append(surf.blit(img, (px+dispx, py+dispy), rect))
        else:
            self.dirtyrect.append(surf.blit(img, (px+dispx, py+dispy)))

def _load_images(playerconfig):
    """Loads the images and stores the surfaces in the config. first image is standig, rest is displayed while moving."""
    global deadimage
    
    for p in playerconfig:
        images = []
        images_flipped = []
        for i_name in p["imgnames"]:
            i = pygame.image.load(i_name).convert()
            col = i.get_at([0, 0]) #Get top left pixel
            i.set_colorkey(col)
            images.append(i)
            images_flipped.append(pygame.transform.flip(i, True, False))
        #The imagenames are stored in the dictionary in "imgnames", we now store in this dicionary under "images" the images and under "images.flipped" the flipped images.
        p["images"] = images
        p["images.flipped"] = images_flipped
    deadimage = pygame.image.load(config.game["player.deadimagename"]).convert()
    col = deadimage.get_at([0,0])
    deadimage.set_colorkey(col)

def Init():
    global player_snd_throw, player_snd_hit, player_snd_die, player_snd_extra    
    global snowball_startvel
    
    config.Status_top("Initialising Players...")
    print "Initialising Players... ",
    #Load Sound Files
    print "Sound: throw,",
    player_snd_throw = config.mixer.loadsnd(config.game["player.sndfile.throw"])
    print "Sound: hit,",
    player_snd_hit   = config.mixer.loadsnd(config.game["player.sndfile.hit"])    
    print "Sound: die,",
    player_snd_die   = config.mixer.loadsnd(config.game["player.sndfile.die"])    
    print "Sound: extra,",
    player_snd_extra = config.mixer.loadsnd(config.game["player.sndfile.extra"])
    print "now the images...",
    _load_images(config.player)
    snowball_startvel = math.cos(math.pi*config.game["player.throwangle"]/180), -math.sin(math.pi*config.game["player.throwangle"]/180)
    print "OK!"

    print "Loading images for Playerstats...",
    PlayerStatus.img_life = pygame.image.load(config.game["playerstatus.lifeimg"])
    PlayerStatus.img_snow = pygame.image.load(config.game["playerstatus.snowimg"])
    PlayerStatus.img_lifeicon  = pygame.image.load(config.game["playerstatus.lifeicon"])
    PlayerStatus.img_snowicon  = pygame.image.load(config.game["playerstatus.snowicon"])
    PlayerStatus.img_powericon = pygame.image.load(config.game["playerstatus.powericon"])
    print "Done!"

